package cn.t.redis.limiter.configuration;

import cn.t.redis.limiter.aspect.RateLimitAspect;
import cn.t.redis.limiter.components.RateLimiter;
import cn.t.redis.limiter.components.RedisOperator;
import cn.t.redis.limiter.components.TokenBucketRateLimiter;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;

/**
 * redis限流器的自动配置类
 */
@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class) // 高优先级，先于自动默认的自动配置生成RedisTemplate
public class LimiterAutoConfiguration {

    /**
     * redis连接工厂,由RedisAutoConfiguration自动配置注入<br/>
     * 2.0以后的springboot默认使用的是LettuceConnectionFactory
     */
    @Autowired
    private RedisConnectionFactory connectionFactory;

    // private LettuceConnectionFactory connectionFactory;

    /**
     * 配置redisTemplate
     * @return redisTemplate
     */
    @Bean
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<String, Object> redisTemplate() {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(this.connectionFactory);

        // 定义Jackson2JsonRedisSerializer序列化对象
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        // 指定要序列化的域，ALL:field,get和set等，ANY: 可见性,会将有private修饰符的字段也序列化
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型，类必须是非final修饰的，final修饰的类，比如String,Integer等会报异常
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // 使用jackson2JsonRedisSerializer序列化和反序列化value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 属性设置完成afterPropertiesSet就会被调用，可以对设置不成功的做一些默认处理
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 辅助工具类,封装了常规的redis操作方法
     * @return redis工具类
     */
    @Bean
    @ConditionalOnBean(RedisTemplate.class)
    public RedisOperator redisOperator() {
        return new RedisOperator(this.redisTemplate());
    }

    /**
     * redis的lua脚本对象
     * @return lua脚本对象
     */
    @Bean
    public DefaultRedisScript<Boolean> redisScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("RateLimiter.lua")));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

    /**
     * 默认限流器的实现-令牌桶
     * @return 默认限流器
     */
    @Bean
    @ConditionalOnMissingBean(RateLimiter.class)
    public RateLimiter rateLimiter() {
        return new TokenBucketRateLimiter(this.redisScript(), this.redisTemplate());
    }

    /**
     * 限流切面
     * @param rateLimiter
     * @return
     */
    @Bean
    @ConditionalOnBean(RateLimiter.class)
    public RateLimitAspect rateLimitAspect(RateLimiter rateLimiter) {
        return new RateLimitAspect(rateLimiter);
    }
}
